﻿using System;
using System.Configuration;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.SqlServer.Dac;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Newtonsoft.Json;

//{"export": {
//	"overwriteblob":"true",
//	"target":
//		{"databasename":"golive_copy", "connectionalias":"DatabaseTargetAlias"},
//	}
//}

namespace wamr.Worker
{
    public class ExportDatabaseTask:ManagerTask
    {

        private bool _overwriteBlob;
        private string _sqlAdminUserName;
        private string _sqlAdminPassword;
        private string _targetServerName;
        private string _targetDatabaseName;
        private string _targetConnectionAlias;

        // wamr's account & container for exported bacpac files
        private static string _storageAccountName;
        private static string _containerName;

        private Task DACServiceCall = null;
        private bool DACServiceCallFailed = false;
        private bool storageInitialized = false;
        private CloudBlobClient cbc = null;

        static ExportDatabaseTask()
        {
            _storageAccountName = CloudConfigurationManager.GetSetting(Settings.WAMR_BACPAC_ACCOUNT_SETTING);
            _containerName = CloudConfigurationManager.GetSetting(Settings.WAMR_BACPAC_CONTAINER_SETTING);
        }

        public ExportDatabaseTask(string ManagerMessage)
        {
            MessageIsValid = true;
            TaskType = "ExportDatabase";

            JSONClasses.JSONExportRootObject r = JsonConvert.DeserializeObject<JSONClasses.JSONExportRootObject>(ManagerMessage);

            _overwriteBlob = (r.export.overwriteblob == "true" ? true : false);

            //
            // target database name is required due to blob naming
            // sql admin creds + target server name OR target connection string are needed
            //

            if (string.IsNullOrEmpty(r.export.target.databasename))
            {
                MessageIsValid = false;
                Trace.TraceError("Target database name must be supplied.");
                return;
            }
            _targetDatabaseName = r.export.target.databasename;

            if (string.IsNullOrEmpty(r.export.sqladminpw) || string.IsNullOrEmpty(r.export.sqladminuser) || string.IsNullOrEmpty(r.export.target.servername))
            {
                if (string.IsNullOrEmpty(r.export.target.connectionalias))
                {
                    MessageIsValid = false;
                    Trace.TraceError("Either sql admin creds or target connection alias must be supplied.");
                    return;
                }
                else
                    _targetConnectionAlias = r.export.target.connectionalias;
            }
            else
            {
                _sqlAdminUserName = r.export.sqladminuser;
                _sqlAdminPassword = r.export.sqladminpw;
                _targetServerName = r.export.target.servername;
            }


            // see if this export object depends on another task
            if (r.export.dependson != null)
            {
                DependsOn = r.export.dependson;
                TaskDisposition = TaskDispositionEnum.WAIT;
            }
        }

        public override void ResolveDependency()
        {
            // resolve the dependency by eliminating the pointer to it.
            DependsOn = null;
        }

        public override bool PreConditionsMet()
        {
            TaskDisposition = TaskDispositionEnum.PROCEED;
            bool retval = true;

            if (!MessageIsValid)
            {
                TaskDisposition = TaskDispositionEnum.FAIL;
                retval = false;
            }

            if (DependsOn != null)
            {
                Trace.TraceInformation("Export task waiting for dependency task to complete.");
                TaskDisposition = TaskDispositionEnum.WAIT;
                retval = false;
            }

            if (!_overwriteBlob)
            {
                if (BlobExists())
                {
                    Trace.TraceInformation("Output blob exists, overwrite not specified.  Export task will not proceed.");
                    TaskDisposition = TaskDispositionEnum.FAIL;
                    retval = false;
                }
            }

            return retval;
        }

        public override bool Initiate()
        {
            DeleteBlob();

            bool retval = ExportDatabase(_sqlAdminUserName, _sqlAdminPassword, _targetServerName, _targetDatabaseName, _targetConnectionAlias);
            if (!retval)
                TaskDisposition = TaskDispositionEnum.FAIL;

            return retval;
        }

        public override bool Status()
        {
            if (DACServiceCallFailed)
            {
                TaskDisposition = TaskDispositionEnum.FAIL;
                return true;
            }

            string localResourceSetting = CloudConfigurationManager.GetSetting(Settings.WAMR_LOCALRESOURCE_SETTING);
            LocalResource storage = RoleEnvironment.GetLocalResource(localResourceSetting);

            FileStream fs = null;
            try
            {
                fs = new FileStream(string.Format(@"{0}\{1}.bacpac", storage.RootPath, _targetDatabaseName), FileMode.Open, FileAccess.ReadWrite);
                fs.Close();
                Trace.TraceInformation("Export to local storage has completed.");
                PersistBacpac();
                TaskDisposition = TaskDispositionEnum.COMPLETE;
            }
            catch (Exception ex)
            {
                Trace.TraceInformation("Waiting for completion of export task.");
            }

            return true;
        }

        private bool ExportDatabase(string sqlUserName, string sqlPassword, string sqlServerName, string inDatabaseName, string connectionAlias)
        {
            bool retval = true;

            string ConnectionString = GetConnectionString(sqlServerName, sqlUserName, sqlPassword, connectionAlias);

            // time the operation
            Stopwatch s = new Stopwatch();
            s.Start();

            DACServiceCall = new Task(() => retval = CallDacServices(ConnectionString, inDatabaseName));
            DACServiceCall.Start();

            // log the duration of the operation, this is the time it takes to start up the export,
            // not the time that it takes to execute the export.
            s.Stop();
            Trace.TraceInformation(string.Format("ExportDatabase({1}.{2}) elapsed time: {0}ms", s.ElapsedMilliseconds, sqlServerName, inDatabaseName));

            return retval;

        }

        private bool CallDacServices(string ConnectionString, string inDatabaseName)
        {
            bool retval = true;
             
            Trace.TraceInformation("Attempting DacServices connection");

            DacServices svc;
            svc = new DacServices(ConnectionString);

            string bacpacPath = BacpacPath();
            try
            {
                svc.ExportBacpac(bacpacPath, inDatabaseName);
            }
            catch (DacServicesException ex)
            {
                Trace.TraceError(string.Format("DacServicesException error attempting to extract bacpac: {0}", ex.Message));
                retval = false;
            }
            catch (Exception ex)
            {
                Trace.TraceError(string.Format("Error exporting database: {0}", ex.Message));
                retval = false;
            }

            if (!retval)
                DACServiceCallFailed = true;

            return retval;
        }

        private string BacpacPath()
        {
            string localResourceSetting = CloudConfigurationManager.GetSetting(Settings.WAMR_LOCALRESOURCE_SETTING);

            LocalResource storage = RoleEnvironment.GetLocalResource(localResourceSetting);
            Trace.TraceInformation(string.Format("Path to ExportLocation is {0}.", storage.RootPath));

            string bacpacPath = string.Format(@"{0}\{1}.bacpac", storage.RootPath, _targetDatabaseName);

            return bacpacPath;
        }

        private bool BlobExists()
        {
            bool retval = true;

            InitializeStorage();
            CloudBlockBlob c = (CloudBlockBlob)cbc.GetBlobReferenceFromServer(GetBlobUri(_targetDatabaseName + ".bacpac"));
            try
            {
                c.FetchAttributes();
            }
            catch (StorageException ex)
            {
                retval = false;
            }

            return retval;
        }

        private bool PersistBacpac()
        {
            InitializeStorage();

            CloudBlobContainer container = cbc.GetContainerReference(_containerName);
            CloudBlockBlob c = container.GetBlockBlobReference(_targetDatabaseName + ".bacpac");
            string bacpacPath = BacpacPath();
            
            using (FileStream f = new FileStream(bacpacPath, FileMode.Open))
            {
                try
                {
                    c.UploadFromStream(f);   // persist the bacpac to blob storage
                }
                catch (Exception ex)
                {
                    Trace.TraceError(string.Format("Failed to Persist Bacpac: {0}", ex.Message));
                    return false;
                }
            }

            try
            {
                File.Delete(bacpacPath);    // delete the bacpac from the local store
            }
            catch (Exception ex)
            {
                Trace.TraceError(string.Format("Failed to delete bacpac from local store: {0}", ex.Message));
                return false;
            }
            
            return true;
        }

        private void DeleteBlob()
        {
            InitializeStorage();

            CloudBlockBlob c = new CloudBlockBlob(GetBlobUri(_targetDatabaseName + ".bacpac"));
            c.DeleteIfExists();
        }

        private Uri GetBlobUri(string fileName)
        {
            if (RoleEnvironment.IsEmulated)
                return new Uri(string.Format("http://127.0.0.1:10000/{1}/{2}", _storageAccountName, _containerName, fileName));
            else
                return new Uri(string.Format("http://{0}.blob.core.windows.net/{1}/{2}", _storageAccountName, _containerName, fileName));
        }

        private string GetConnectionString(string serverName, string userName, string password, string connectionAlias)
        {
            string cs;

            if (string.IsNullOrEmpty(connectionAlias))
            {
                SqlConnectionStringBuilder connString1Builder;
                connString1Builder = new SqlConnectionStringBuilder();
                if (serverName == "localdb")
                    connString1Builder.DataSource = @"(localdb)\v11.0";
                else
                    connString1Builder.DataSource = string.Format("{0}.database.windows.net", serverName);

                connString1Builder.InitialCatalog = "master";
                connString1Builder.Encrypt = true;
                connString1Builder.TrustServerCertificate = false;
                connString1Builder.UserID = userName;
                connString1Builder.Password = password;
                cs = connString1Builder.ConnectionString;
            }
            else
            {
                ConnectionStringSettingsCollection settings = ConfigurationManager.ConnectionStrings;
                cs = settings[connectionAlias].ConnectionString;
            }

            return cs;
        }

        private void InitializeStorage()
        {
            if (storageInitialized)
            {
                return;
            }

            try
            {
                // parse the connection string & create object
                Microsoft.WindowsAzure.Storage.CloudStorageAccount storageAccount;
                if (Microsoft.WindowsAzure.Storage.CloudStorageAccount.TryParse(_storageAccountName, out storageAccount))
                {
                    // create the blob storage client
                    cbc = storageAccount.CreateCloudBlobClient();

                    // and the container for bacpac's
                    CloudBlobContainer container = cbc.GetContainerReference(_containerName);
                    container.CreateIfNotExists();
                }

            }
            catch (Exception ex)
            {
                Trace.TraceError(string.Format("Error initializing storage: {0}", ex.InnerException));
                return;
            }

            storageInitialized = true;
        }

    }
}
